package com.wmcsk.dl.dl.internal;

import android.annotation.TargetApi;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.os.Build;
import android.text.TextUtils;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;

import dalvik.system.DexClassLoader;
import com.wmcsk.dl.dl.DLActivity;
import com.wmcsk.dl.dl.DLBroadcastReciver;
import com.wmcsk.dl.dl.DLFragmentActivity;
import com.wmcsk.dl.dl.DLProxyActivity;
import com.wmcsk.dl.dl.DLProxyBroadcastReceiver;
import com.wmcsk.dl.dl.DLProxyFragmentActivity;
import com.wmcsk.dl.dl.DLProxyService;
import com.wmcsk.dl.dl.DLProxyServiceOne;
import com.wmcsk.dl.dl.DLProxyServiceThree;
import com.wmcsk.dl.dl.DLProxyServiceTwo;
import com.wmcsk.dl.dl.DLService;
import com.wmcsk.dl.dl.DLServiceOne;
import com.wmcsk.dl.dl.DLServiceThree;
import com.wmcsk.dl.dl.DLServiceTwo;
import com.wmcsk.dl.dl.DLSingleTaskActivity;
import com.wmcsk.dl.dl.DLSingleTaskProxyActivity;
import com.wmcsk.dl.dl.DLTranslucentActivity;
import com.wmcsk.dl.dl.DLTranslucentProxyActivity;
import com.wmcsk.dl.dl.utils.DL;
import com.wmcsk.dl.dl.utils.DLConstants;
import com.wmcsk.dl.dl.utils.SoLibManager;

public class DLPluginManager {
	private static final String TAG = DLPluginManager.class.getName();
	/**
     * return value of {@link #startPluginActivity(Activity, DLIntent)} start
     * success
     */
    public static final int START_RESULT_SUCCESS = 0;

    /**
     * return value of {@link #startPluginActivity(Activity, DLIntent)} package
     * not found
     */
    public static final int START_RESULT_NO_PKG = 1;

    /**
     * return value of {@link #startPluginActivity(Activity, DLIntent)} class
     * not found
     */
    public static final int START_RESULT_NO_CLASS = 2;

    /**
     * return value of {@link #startPluginActivity(Activity, DLIntent)} class
     * type error
     */
    public static final int START_RESULT_TYPE_ERROR = 3;

    private static DLPluginManager sInstance;
    private Context mContext;
    private final HashMap<String, DLPluginPackage> mPackagesHolder = new HashMap<String, DLPluginPackage>();

    private int mFrom = DLConstants.FROM_INTERNAL;

    private String mNativeLibDir = null;

    private int mResult;

    private DLPluginManager(Context context) {
        mContext = context.getApplicationContext();
        mNativeLibDir = mContext.getDir("pluginlib", Context.MODE_PRIVATE).getAbsolutePath();
    }

    public static DLPluginManager getInstance(Context context) {
        if (sInstance == null) {
            synchronized (DLPluginManager.class) {
                if (sInstance == null) {
                    sInstance = new DLPluginManager(context);
                }
            }
        }

        return sInstance;
    }

    /**
     * Load a apk. Before start a plugin Activity, we should do this first.<br/>
     * NOTE : will only be called by host apk.
     *
     * @param dexPath
     */
    public DLPluginPackage loadApk(String dexPath) {
        // when loadApk is called by host apk, we assume that plugin is invoked
        // by host.
        return loadApk(dexPath, true);
    }

    /**
     * @param dexPath
     *            plugin path
     * @param hasSoLib
     *            whether exist so lib in plugin
     * @return
     */
    public DLPluginPackage loadApk(final String dexPath, boolean hasSoLib) {
        long time = System.currentTimeMillis();
        mFrom = DLConstants.FROM_EXTERNAL;

        PackageInfo packageInfo = mContext.getPackageManager().getPackageArchiveInfo(dexPath,
                PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES);
        if (packageInfo == null) {
            return null;
        }
        DLPluginPackage pluginPackage = preparePluginEnv(packageInfo, dexPath);
        if (hasSoLib) {
            copySoLib(dexPath);
        }
        DL.e("加载一个插件耗时："+dexPath+(System.currentTimeMillis() - time));
        return pluginPackage;
    }

    /**
     * prepare plugin runtime env, has DexClassLoader, Resources, and so on.
     * @param packageInfo
     * @param dexPath
     * @return
     */
    private DLPluginPackage preparePluginEnv(PackageInfo packageInfo, String dexPath) {
        DLPluginPackage pluginPackage = mPackagesHolder.get(packageInfo.packageName);
        if (pluginPackage != null) {
        	DL.e("复用已加载插件"+packageInfo.packageName);
            return pluginPackage;
        }
        DL.e("重新加载插件："+packageInfo.packageName);
        DexClassLoader dexClassLoader = createDexClassLoader(dexPath);
        AssetManager assetManager = createAssetManager(dexPath);
        Resources resources = createResources(assetManager);
        // create pluginPackage
        pluginPackage = new DLPluginPackage(dexClassLoader, resources, packageInfo);
        mPackagesHolder.put(packageInfo.packageName, pluginPackage);
        return pluginPackage;
    }

    private String dexOutputPath;

    private DexClassLoader createDexClassLoader(String dexPath) {
        File dexOutputDir = mContext.getDir("dex", Context.MODE_PRIVATE);
        dexOutputPath = dexOutputDir.getAbsolutePath();
        DexClassLoader loader = new DexClassLoader(dexPath, dexOutputPath, mNativeLibDir, mContext.getClassLoader());
        return loader;
    }

    private AssetManager createAssetManager(String dexPath) {
        try {
            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
            addAssetPath.invoke(assetManager, dexPath);
            return assetManager;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }

    }

    public DLPluginPackage getPackage(String packageName) {
        return mPackagesHolder.get(packageName);
    }

    private Resources createResources(AssetManager assetManager) {
        Resources superRes = mContext.getResources();
        Resources resources = new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
        return resources;
    }

    /**
     * copy .so file to pluginlib dir.
     * @param dexPath
     * @param hasSoLib
     */
    private void copySoLib(String dexPath) {
        // TODO: copy so lib async will lead to bugs maybe, waiting for
        // resolved later.

        // TODO : use wait and signal is ok ? that means when copying the
        // .so files, the main thread will enter waiting status, when the
        // copy is done, send a signal to the main thread.
        // new Thread(new CopySoRunnable(dexPath)).start();

        SoLibManager.getSoLoader().copyPluginSoLib(mContext, dexPath, mNativeLibDir);
    }

    /**
     * {@link #startPluginActivityForResult(Activity, DLIntent, int)}
     */
    public int startPluginActivity(Context context, DLIntent dlIntent) {
        return startPluginActivityForResult(context, dlIntent, -1);
    }

    /**
     * @param context
     * @param dlIntent
     * @param requestCode
     * @return One of below: {@link #START_RESULT_SUCCESS}
     *         {@link #START_RESULT_NO_PKG} {@link #START_RESULT_NO_CLASS}
     *         {@link #START_RESULT_TYPE_ERROR}
     */
    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    public int startPluginActivityForResult(Context context, DLIntent dlIntent, int requestCode) {
        DL.e("context="+context);

        if (mFrom == DLConstants.FROM_INTERNAL) {
            dlIntent.setClassName(context, dlIntent.getPluginClass());
            performStartActivityForResult(context, dlIntent, requestCode);
            return DLPluginManager.START_RESULT_SUCCESS;
        }

        String packageName = dlIntent.getPluginPackage();
        if (TextUtils.isEmpty(packageName)) {//包名是空的，则默认使用当前插件的包名
//           throw new NullPointerException("disallow null packageName.");
        	if(context instanceof DLAttachable){
        		packageName=((DLAttachable)context).getPluginPackageName();
        	}
        }
        DLPluginPackage pluginPackage = mPackagesHolder.get(packageName);
        if (pluginPackage == null) {
            return START_RESULT_NO_PKG;
        }
        //获取完整类名，替换以点开头的
        final String className = getPluginActivityFullPath(dlIntent, pluginPackage);
        Class<?> clazz = loadPluginClass(pluginPackage.classLoader, className);
        if (clazz == null) {
            return START_RESULT_NO_CLASS;
        }

        // get the proxy activity class, the proxy activity will launch the
        // plugin activity.
        Class<? extends Activity> activityClass = getProxyActivityClass(clazz);
        if (activityClass == null) {
            return START_RESULT_TYPE_ERROR;
        }

        // put extra data
        dlIntent.putExtra(DLConstants.EXTRA_CLASS, className);
        dlIntent.putExtra(DLConstants.EXTRA_PACKAGE, packageName);
        dlIntent.setClass(mContext, activityClass);
        performStartActivityForResult(context, dlIntent, requestCode);
        return START_RESULT_SUCCESS;
    }

    public int startPluginService(final Context context, final DLIntent dlIntent) {
        if (mFrom == DLConstants.FROM_INTERNAL) {
            dlIntent.setClassName(context, dlIntent.getPluginClass());
            context.startService(dlIntent);
            return DLPluginManager.START_RESULT_SUCCESS;
        }
        //修改，如果用户传的是null的包名，则默认使用当前所在插件的包名
        if(TextUtils.isEmpty(dlIntent.getPluginPackage())){
            if(context instanceof DLAttachable){
                String packageName=((DLAttachable)context).getPluginPackageName();
                dlIntent.setPluginPackage(packageName);
            }
        }

        fetchProxyServiceClass(dlIntent, new OnFetchProxyServiceClass() {
            @Override
            public void onFetch(int result, Class<? extends Service> proxyServiceClass) {
                // TODO Auto-generated method stub
                if (result == START_RESULT_SUCCESS) {
                    dlIntent.setClass(context, proxyServiceClass);
                    // start代理Service
                    context.startService(dlIntent);
                }
                mResult = result;
            }
        });

        return mResult;
    }

    /**
     * 停止服务
     * @param context
     * @param dlIntent
     * @return
     */
    public int stopPluginService(final Context context, final DLIntent dlIntent) {
        if (mFrom == DLConstants.FROM_INTERNAL) {
            dlIntent.setClassName(context, dlIntent.getPluginClass());
            context.stopService(dlIntent);
            return DLPluginManager.START_RESULT_SUCCESS;
        }

        fetchProxyServiceClass(dlIntent, new OnFetchProxyServiceClass() {
            @Override
            public void onFetch(int result, Class<? extends Service> proxyServiceClass) {
                // TODO Auto-generated method stub
                if (result == START_RESULT_SUCCESS) {
                    dlIntent.setClass(context, proxyServiceClass);
                    // stop代理Service
                    context.stopService(dlIntent);
                }
                mResult = result;
            }
        });

        return mResult;
    }


    public int bindPluginService(final Context context, final DLIntent dlIntent, final ServiceConnection conn,
                                 final int flags) {
        if (mFrom == DLConstants.FROM_INTERNAL) {
            dlIntent.setClassName(context, dlIntent.getPluginClass());
            context.bindService(dlIntent, conn, flags);
            return DLPluginManager.START_RESULT_SUCCESS;
        }
        //修改，如果用户传的是null的包名，则默认使用当前所在插件的包名
        if(TextUtils.isEmpty(dlIntent.getPluginPackage())){
            if(context instanceof DLAttachable){
                String packageName=((DLAttachable)context).getPluginPackageName();
                dlIntent.setPluginPackage(packageName);
            }
        }
        fetchProxyServiceClass(dlIntent, new OnFetchProxyServiceClass() {
            @Override
            public void onFetch(int result, Class<? extends Service> proxyServiceClass) {
                // TODO Auto-generated method stub
                if (result == START_RESULT_SUCCESS) {
			        dlIntent.setClass(context, proxyServiceClass);
                    // Bind代理Service
                    context.bindService(dlIntent, conn, flags);
                }
                mResult = result;
            }
        });

        return mResult;
    }

    public int unBindPluginService(final Context context, DLIntent dlIntent, final ServiceConnection conn) {
        if (mFrom == DLConstants.FROM_INTERNAL) {
            context.unbindService(conn);
            return DLPluginManager.START_RESULT_SUCCESS;
        }
        //修改，如果用户传的是null的包名，则默认使用当前所在插件的包名
        if(TextUtils.isEmpty(dlIntent.getPluginPackage())){
            if(context instanceof DLAttachable){
                String packageName=((DLAttachable)context).getPluginPackageName();
                dlIntent.setPluginPackage(packageName);
            }
        }
        fetchProxyServiceClass(dlIntent, new OnFetchProxyServiceClass() {
            @Override
            public void onFetch(int result, Class<? extends Service> proxyServiceClass) {
                // TODO Auto-generated method stub
                if (result == START_RESULT_SUCCESS) {
                    // unBind代理Service
                    context.unbindService(conn);
                }
                mResult = result;
            }
        });
        return mResult;

    }

    /**
     * 获取代理ServiceClass
     * @param dlIntent
     * @param fetchProxyServiceClass
     */
    private void fetchProxyServiceClass(DLIntent dlIntent, OnFetchProxyServiceClass fetchProxyServiceClass) {
        String packageName = dlIntent.getPluginPackage();
        if (TextUtils.isEmpty(packageName)) {
            throw new NullPointerException("disallow null packageName.");
        }
        DLPluginPackage pluginPackage = mPackagesHolder.get(packageName);
        if (pluginPackage == null) {
            fetchProxyServiceClass.onFetch(START_RESULT_NO_PKG, null);
            return;
        }

        // 获取要启动的Service的全名
        String className = dlIntent.getPluginClass();
        Class<?> clazz = loadPluginClass(pluginPackage.classLoader, className);
        if (clazz == null) {
            fetchProxyServiceClass.onFetch(START_RESULT_NO_CLASS, null);
            return;
        }

        Class<? extends Service> proxyServiceClass = getProxyServiceClass(clazz);
        if (proxyServiceClass == null) {
            fetchProxyServiceClass.onFetch(START_RESULT_TYPE_ERROR, null);
            return;
        }

        // put extra data
        dlIntent.putExtra(DLConstants.EXTRA_CLASS, className);
        dlIntent.putExtra(DLConstants.EXTRA_PACKAGE, packageName);
        fetchProxyServiceClass.onFetch(START_RESULT_SUCCESS, proxyServiceClass);
    }


    private Class<?> loadPluginClass(ClassLoader classLoader, String className) {
        Class<?> clazz = null;
        try {
            clazz = Class.forName(className, true, classLoader);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        return clazz;
    }

    private String getPluginActivityFullPath(DLIntent dlIntent, DLPluginPackage pluginPackage) {
        String className = dlIntent.getPluginClass();
        className = (className == null ? pluginPackage.defaultActivity : className);
        if (className.startsWith(".")) {
            className = dlIntent.getPluginPackage() + className;
        }
        return className;
    }

    /**
     * get the proxy activity class, the proxy activity will delegate the plugin
     * activity
     *
     * @param clazz
     *            target activity's class   决定使用那个代理
     * @return
     */
    private Class<? extends Activity> getProxyActivityClass(Class<?> clazz) {
        Class<? extends Activity> activityClass = null;
        if (DLActivity.class.isAssignableFrom(clazz)) {
            activityClass = DLProxyActivity.class;
        } else if (DLFragmentActivity.class.isAssignableFrom(clazz)) {
            activityClass = DLProxyFragmentActivity.class;
        }else if(DLSingleTaskActivity.class.isAssignableFrom(clazz)){ //增加登录专用的启动模式的activity
            activityClass = DLSingleTaskProxyActivity.class;
        }else if(DLTranslucentActivity.class.isAssignableFrom(clazz)){//增加透明专用的启动模式的activity
            activityClass = DLTranslucentProxyActivity.class;
        }
        return activityClass;
    }

    private Class<? extends Service> getProxyServiceClass(Class<?> clazz) {
        Class<? extends Service> proxyServiceClass = null;
        if(DLServiceOne.class.isAssignableFrom(clazz)){
            proxyServiceClass = DLProxyServiceOne.class;
        }else if(DLServiceTwo.class.isAssignableFrom(clazz)){
            proxyServiceClass = DLProxyServiceTwo.class;
        }else if(DLServiceThree.class.isAssignableFrom(clazz)){
            proxyServiceClass = DLProxyServiceThree.class;
        }else if(DLService.class.isAssignableFrom(clazz)){
            proxyServiceClass = DLProxyService.class;
        }
        // 后续可能还有IntentService，待补充

        return proxyServiceClass;
    }

    private void performStartActivityForResult(Context context, DLIntent dlIntent, int requestCode) {
        DL.d(TAG, "launch " + dlIntent.getPluginClass());
        if (context instanceof Activity) {
            ((Activity) context).startActivityForResult(dlIntent, requestCode);
        } else {
            context.startActivity(dlIntent);
        }
    }

    private interface OnFetchProxyServiceClass {
        public void onFetch(int result, Class<? extends Service> proxyServiceClass);
    }
    /**
     * 根据插件包名移除已加载的环境
     * @param packageName
     */
    public void removePluginByPackageName(String packageName){
    	if(mPackagesHolder.containsKey(packageName)){
        	mPackagesHolder.remove(packageName);
    	}
    }
    /**
     * 清空所有已加载的插件环境
     *
     */
    public void removePluginAll(){
    	mPackagesHolder.clear();
    }
    /**
     * 判断插件是否已加载
     * @param packageName
     * @return
     */
    public boolean isLoaded(String packageName){
    	return mPackagesHolder.containsKey(packageName);
    }

    /**
     * 获取代理BroadcastReceiver
     * @param clazz
     * @return
     */
    public Class<? extends BroadcastReceiver> findProxyBroadcastReceiver(Class<?> clazz){
        Class<? extends BroadcastReceiver> proxyBroadcastReceiverClass = null;
        if (DLBroadcastReciver.class.isAssignableFrom(clazz)) {
            proxyBroadcastReceiverClass = DLProxyBroadcastReceiver.class;
        }
        //后续可以添加
        return proxyBroadcastReceiverClass;
    }

    /**
     * 代码注册广播
     * @param context
     * @param intentFilter
     * @return
     */
    public int registerBroadcast(Context context, DLIntent dlIntent, IntentFilter intentFilter){
        if (mFrom == DLConstants.FROM_INTERNAL) {
            try {
                DL.e("开始注册广播---内部");
                String broadcastReceiver = dlIntent.getPluginClass();
                Class clazz = Class.forName(broadcastReceiver,false,context.getClassLoader());
                BroadcastReceiver broadCastReciver = (BroadcastReceiver)clazz.newInstance();
                context.registerReceiver(broadCastReciver,intentFilter);
                return DLPluginManager.START_RESULT_SUCCESS;
            }catch (Exception e){
                e.getMessage();
                DL.e("开始获取广播"+e.getMessage());
                return DLPluginManager.START_RESULT_NO_PKG;
            }
        }else{
            DL.e("开始注册广播---外部");
            String packageName = dlIntent.getPluginPackage();
            if(TextUtils.isEmpty(dlIntent.getPluginPackage())){
                if(context instanceof DLAttachable){
                    packageName=((DLAttachable)context).getPluginPackageName();
                    dlIntent.setPluginPackage(packageName);
                }
            }
            DL.e("开始注册广播"+packageName);
            if (TextUtils.isEmpty(packageName)) {
                throw new NullPointerException("disallow null packageName.");
            }
            DLPluginPackage pluginPackage = mPackagesHolder.get(packageName);
            DL.e("开始注册广播"+pluginPackage);
            if (pluginPackage == null) {
                return DLPluginManager.START_RESULT_NO_PKG;
            }
            try {
                String broadcastReceiver = dlIntent.getPluginClass();
                DL.e("开始注册广播"+broadcastReceiver);
                Class clazz = Class.forName(broadcastReceiver,false,pluginPackage.classLoader);
                Class<? extends BroadcastReceiver> proxyBroadcastReceiver = findProxyBroadcastReceiver(clazz);
                DL.e("开始获取代理广播"+proxyBroadcastReceiver);
                if(proxyBroadcastReceiver == null){
                    return DLPluginManager.START_RESULT_TYPE_ERROR;
                }
                dlIntent.putExtra(DLConstants.EXTRA_CLASS, broadcastReceiver);
                dlIntent.putExtra(DLConstants.EXTRA_PACKAGE, packageName);
                Constructor constractor = proxyBroadcastReceiver.getConstructor(Context.class, Intent.class);
                constractor.setAccessible(true);
                BroadcastReceiver broadCastReciver = (BroadcastReceiver) constractor.newInstance(context,dlIntent);
                context.registerReceiver(broadCastReciver,intentFilter);
                DL.e("开始注册广播"+broadCastReciver);
                return DLPluginManager.START_RESULT_SUCCESS;
            }catch (Exception e){
                DL.e("开始获取代理广播"+e.getMessage());
                return DLPluginManager.START_RESULT_NO_PKG;
            }
        }
    }

    /**
     * 判断服务是否死亡
     * @param context
     * @param serviceName
     * @param dlIntent
     * @return
     */
    public int isServiveAlive(Context context, String serviceName, DLIntent dlIntent){
        if (mFrom == DLConstants.FROM_INTERNAL){
            return isServiceWork(context,serviceName)? DLPluginManager.START_RESULT_SUCCESS: DLPluginManager.START_RESULT_TYPE_ERROR;
        }else{
            //修改，如果用户传的是null的包名，则默认使用当前所在插件的包名
            if(TextUtils.isEmpty(dlIntent.getPluginPackage())){
                if(context instanceof DLAttachable){
                    String packageName=((DLAttachable)context).getPluginPackageName();
                    dlIntent.setPluginPackage(packageName);
                }
            }
            String packageName = dlIntent.getPluginPackage();
            if (TextUtils.isEmpty(packageName)) {
                DL.e("packageName is null");
                throw new NullPointerException("disallow null packageName.");
            }
            DLPluginPackage pluginPackage = mPackagesHolder.get(packageName);
            String className = dlIntent.getPluginClass();
            Class<?> clazz = loadPluginClass(pluginPackage.classLoader, className);
            if (clazz == null) {
                return DLPluginManager.START_RESULT_NO_CLASS;
            }
            Class proxyClazz = getProxyServiceClass(clazz);
            DL.e("proxyClass:"+proxyClazz);
            if(proxyClazz == null){
                return DLPluginManager.START_RESULT_NO_CLASS;
            }
            return isServiceWork(context,proxyClazz.getName())? DLPluginManager.START_RESULT_SUCCESS: DLPluginManager.START_RESULT_TYPE_ERROR;
        }
    }

    /**
     * 判断某个服务是否正在运行的方法
     *
     * @param mContext
     * @param serviceName
     *            是包名+服务的类名（例如：net.loonggg.testbackstage.TestService）
     * @return true代表正在运行，false代表服务没有正在运行
     */
    private boolean isServiceWork(Context mContext, String serviceName) {
        boolean isWork = false;
        ActivityManager myAM = (ActivityManager) mContext
                .getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningServiceInfo> myList = myAM.getRunningServices(150);
        if (myList.size() <= 0) {
            return false;
        }
        for (int i = 0; i < myList.size(); i++) {
            String mName = myList.get(i).service.getClassName().toString();
            if (mName.equals(serviceName)) {
                isWork = true;
                break;
            }
        }
        return isWork;
    }
}
